package com.wang.tool.lock;

import com.wang.tool.common.SimpleException;
import com.wang.tool.lock.core.LockBody;
import com.wang.tool.lock.core.LockConfig;
import com.wang.tool.lock.core.LockEventTypeEnum;
import com.wang.tool.lock.core.LockException;
import com.wang.tool.monitor.InvokeTemplate;
import com.wang.tool.monitor.LinkLogUtil;
import com.wang.tool.monitor.core.MonitorInfo;
import com.wang.tool.monitor.template.MonitorInvoker;

import java.util.function.Supplier;

/**
 * @Description 锁服务
 * @Author shaopeng
 * @Date 2022/6/10
 * @Version 1.0
 */
public abstract class LockServer {


    /**
     * 加锁执行
     *
     * @param body   锁主体,非空
     * @param action 执行器
     * @param <T>
     * @return
     * @throws IllegalArgumentException 参数异常
     * @throws SimpleException          业务异常
     */
    public <T> T lockDo(LockBody body, Supplier<T> action) throws LockException, IllegalArgumentException {
        if (body == null || body.getKey() == null || body.getNamespace()==null || body.getLockSecond() <= 0 || body.getLockWaitTime() < 0 || action == null) {
            throw new IllegalArgumentException("lockBody/action can not be null");
        }

        String namespace = body.getNamespace();
        LockConfig lockConfig = LockConfigCenter.getLockConfig(namespace);
        if (!lockConfig.isOpen()) {
            LinkLogUtil.info("lock is close,{}",namespace);
            return action.get();
        }

        String key = body.getKey();
        int lockSecond = body.getLockSecond();
        return InvokeTemplate.monitor("LockServer", namespace, "lockDo", new MonitorInvoker<T>() {
            private boolean lockSuccess = false;

            @Override
            public void weaveOnBefore(MonitorInfo<T> monitorInfo) {
                monitorInfo.addDigest("key", key);
            }

            @Override
            public T internalCall() {
                //加锁
                try {
                    tryLock(namespace, key, lockSecond, body.getLockWaitTime());
                    fireEvent(LockEventTypeEnum.LOCK_SUCCESS, namespace, key, null);
                } catch (RuntimeException e) {
                    fireEvent(LockEventTypeEnum.LOCK_FAIL, namespace, key, e);
                }
                lockSuccess = true;
                //执行业务
                T ret = action.get();
                try {
                    //尝试解锁
                    unLock(namespace, key);
                    fireEvent(LockEventTypeEnum.UN_LOCK_SUCCESS, namespace, key, null);
                } catch (RuntimeException e) {
                    fireEvent(LockEventTypeEnum.UN_LOCK_FAIL, namespace, key, e);
                }
                return ret;
            }

            @Override
            public void weaveOnAfter(MonitorInfo<T> monitorInfo) {
                monitorInfo.addDigest("lockSuccess", lockSuccess);
                monitorInfo.addDigest("timeout", String.valueOf(monitorInfo.getCost() / 1000 <= lockSecond));
            }
        });

    }


    /**
     * 尝试加锁
     *
     * @param namespace      锁主体 非空
     * @param key        key
     * @param lockSecond 锁时长 >=0  0代表永久
     * @param waitTime   >=0
     * @return 不抛异常则代表为成功
     */
    protected abstract void tryLock(String namespace, String key, int lockSecond, int waitTime) throws LockException;


    /**
     * 解锁
     *
     * @param namespace 域 非空
     * @param key   锁key 非空
     */
    protected abstract void unLock(String namespace, String key);

    protected void fireEvent(LockEventTypeEnum eventType, String namespace, String key, RuntimeException e) {
        if (eventType == LockEventTypeEnum.LOCK_FAIL || eventType==LockEventTypeEnum.UN_LOCK_FAIL) {
            throw new LockException(eventType.name(), e, "fail, namespace:" + namespace + ", key:" + key);
        }
    }
}
